#include <math.h>
#include <iostream>
#include "../include/scene.h"

Scene::Scene() {
	scache = 0;
	obj_list = (Object *) 0;
}

void Scene::addObject(Object &obj) {
	obj.link(obj_list);
	obj_list = &obj;
}

void Scene::addLight(Light &lt) {
	lt.link(light_list);
	light_list = &lt;
}

Colour Scene::raytrace(Ray &ray, int level) {
	float ta, t;
	Colour col;
	Object *obj;
	Object *closest;
	Light *lt;
	Hit hit;
	Material obj_mat;
	Vertex position;
	Vector normal;

	if (level == 0) {
		col.clear();
		return col; // stop if recursed deep enough
	}

	// TEST EACH OBJECT AGAINST RAY, FIND THE CLOSEST

	t = 1000000000.0; // a long way aways
	closest = (Object *) 0;
	obj = obj_list;

	while (obj != (Object *) 0) {
		if (obj->intersect(ray, &hit) == true) {
			if (hit.t < t) {
				closest = hit.obj;
				t = hit.t;
				normal = hit.n;
				position = hit.p;
				obj_mat = hit.obj_mat;
			}
		}

		obj = obj->next();
	}

	col.clear();

	if (closest != (Object *) 0) {
		lt = light_list;

		Colour ka;
		Colour kd;
		Colour ks;
		Colour kr;
		Colour kt;
		ka = obj_mat.ka;
		kd = obj_mat.kd;
		ks = obj_mat.ks;
		kr = obj_mat.kr;
		kt = obj_mat.kt;

//		 ka = closest->obj_mat->ka;
//		 kd = closest->obj_mat->kd;
//		 ks = closest->obj_mat->ks;
//		 kr = closest->obj_mat->kr;
//		 kt = closest->obj_mat->kt;

		//		Colour ka = closest->obj_mat->ka;
		//		Colour kd = closest->obj_mat->kd;
		//		Colour ks = closest->obj_mat->ks;
		//		Colour kr = closest->obj_mat->kr;
		//		Colour kt = closest->obj_mat->kt;


		Ray sray;

		bool firstLightFlag = 1;
		while (lt != (Light *) 0) {
			Vector ldir;
			Vector xldir;
			Colour lcol;

			// add shadow test here
			// calculate diffuse component

			lt->getLightProperties(position, &xldir, &lcol);

			xldir.normalise();

			//shadow
			bool inShadowFlag = false;
			if (shadowFlag != 0) {
				Ray shardowRay;
				//			shardowRay.P = position;//lt->getPosition(position);
				//			Vector smalldistancelight = xldir.multiply(-0.01);// this value can be used for solving the salt-and-pepper issue.
				//			shardowRay.P.set(position.x + smalldistancelight.x,
				//									position.y + smalldistancelight.y,
				//									position.z + smalldistancelight.z, position.w);

				Vector smalldistanceCloseNormalLight = normal.multiply(-0.04);

				Vertex positioncloseNormal = position;
				//			positioncloseNormal.set(position.x + smalldistanceCloseNormalLight.x,
				//								position.y + smalldistanceCloseNormalLight.y,
				//								position.z + smalldistanceCloseNormalLight.z, position.w);

				Vector xldir1;
				Colour lcol1;
				lt->getLightProperties(positioncloseNormal, &xldir1, &lcol1);
				shardowRay.P = lt->getPosition(positioncloseNormal);

				if (shardowRay.P.x == 999999) {
					//....
				}
				shardowRay.D = xldir1.multiply(-1.0);
				double shadowraydistance = shardowRay.P.distance(position)
						- 0.01;
				inShadowFlag = shadowtrace(shardowRay, closest,
						shadowraydistance);
			}

			//shadow end


			float dlc = xldir.dot(normal);//dlc stands for what?

			if (dlc < 0.0) {
				if (typeid(closest) == typeid(Quadric)) {
					normal = normal.multiply(-1.0);
					dlc = xldir.dot(normal);
				}
			}

			if (dlc < 0.0) {
				dlc = 0.0;
			}

			float slc = 0.0;
			if (specularFlag != 0) {
				// calculate specular component here
				//R = I – 2.0 * (I.N) * N
				//I = Ilks(R.V)n  v is view drection.  n typical values 20-200.
				double ndoti = xldir.dot(normal);//light.dot(normal)
				Vector reflec = xldir.subtract(
						normal.multiply(ndoti).multiply(2.0));
				double colourBeforePow = reflec.dot(ray.D);
				Colour l = Colour();
				if (colourBeforePow > 0.0) {
					double colourAfterPow = pow(colourBeforePow, 30);
					l = ks.multiply(colourAfterPow);
					slc = colourAfterPow;
				}
				//				col.red += l.red;
				//				col.green += l.green;
				//				col.blue += l.blue;
			}
			if (!inShadowFlag) {
				// combine components
				if (firstLightFlag == 1) {
					col.red += ka.red + lcol.red
							* (dlc * kd.red + slc * ks.red);
					col.green += ka.green + lcol.green * (dlc * kd.green + slc
							* ks.green);
					col.blue += ka.blue + lcol.blue * (dlc * kd.blue + slc
							* ks.blue);
					firstLightFlag = 0;
				} else {
					col.red += lcol.red * (dlc * kd.red + slc * ks.red);
					col.green += lcol.green * (dlc * kd.green + slc * ks.green);
					col.blue += lcol.blue * (dlc * kd.blue + slc * ks.blue);
				}

			}
			//			if(shadowflag){
			//	//				col.set(0.9,0.1,0.1,1);
			//	//				return col;
			//				col = col.multiply(0.8);
			//			}

			lt = lt->next(); // next light
		}

		int totalInternalReflection = -1;
		double krr = 999;
		if (transparentFlag != 0) {
			// add refracted rays here
			if (closest->refraction < 9990) {
				Ray ray1 = ray;
				ray1.D.set(-ray1.D.x, -ray1.D.y, -ray1.D.z);

				Vector normal1 = normal;
				double re = closest->refraction;

				//    	  T = 1/η * I – (cos θt – (1/η)* cos θi ) * N
				//    			  cos θi = N.I
				//    	  cos θt = sqrt(1 – (1/η2)*(1-cos2 θi))
				//
				double cosi = ray1.D.dot(normal1);
				//				cosi = -cosi;
				if (cosi < 0.0) {
					normal1.set(-normal1.x, -normal1.y, -normal1.z);
					//					cosi = ray1.D.dot(normal1);
					cosi = -cosi;
					re = 1.0 / re;
				} else {
				}
				//cos θt = sqrt(1 – (1/η2)*(1-cos2 θi))
				double tempbeforecost = 1.0 - ((1.0 - cosi * cosi) / (re * re));

				if (tempbeforecost < 0) {
					totalInternalReflection = 1;
				}

				double cost = sqrt(tempbeforecost);

				double tempbeforeD = cost - (cosi / re);

				Vector temp1 = ray1.D.multiply(-1.0 / re);
				Vector temp2 = temp1.subtract(normal1.multiply(tempbeforeD));

				//				ttray.D = ray1.D.multiply(1.0 / re).subtract(
				//										normal1.multiply(tempbeforeD));
				Ray ttray;
				//				ttray.D = ray1.D.multiply(1.0 / re).subtract(
				//						normal1.multiply(tempbeforeD));
				ttray.D = temp2;

				ttray.D.normalise();

				Vector smalldistance = ttray.D.multiply(0.01);// this value can be used for solving the salt-and-pepper issue.
				ttray.P.set(position.x + smalldistance.x,
						position.y + smalldistance.y,
						position.z + smalldistance.z, position.w);

				Colour ttrayCol = raytrace(ttray, level - 1);
				//			col = col.add(ttrayCol.multiply(0.5));
				//			col = ttrayCol;
				//				col += hit.object.kt * raytrace(tray, depth-1);
				//				col = col.add(ttrayCol.multiply(0.0));//kt = 0.5
				//				col = ttrayCol.multiply(kt);//kt = 0.5

				double kr_pra = (re * cosi - cost) / (re * cosi + cost);
				double kr_per = (cosi - re * cost) / (cosi + re * cost);
				krr = 0.5 * (kr_pra * kr_pra + kr_per * kr_per);

				if (totalInternalReflection != 1) {
					Colour cotemp = ttrayCol.multiply(1.0 - krr).multiply(
							fabs(ttray.D.dot(normal1)));
					col = col.add(cotemp);//kt = 0.5
				}
			}

		}

		// add reflected rays here
		if (reflectionFlag != 0 || totalInternalReflection == 1
				|| transparentFlag != 0) {
			// calculate specular component here
			//R = E – 2.0 * (N.E) * N
			//I = Ilks(R.V)n  v is view drection.  n typical values 20-200.
			Vector normal1 = normal;
			double ndote = ray.D.dot(normal1);//light.dot(normal)


			if (ndote > 0.0) {
				normal1.set(-normal1.x, -normal1.y, -normal1.z);
				ndote = -ndote;
			} else {
			}

			Vector rrayD =
					ray.D.subtract(normal1.multiply(ndote).multiply(2.0));
			Ray rtray;
			rtray.D = rrayD;
			rtray.D.normalise();

			Vector smalldistancer = rtray.D.multiply(0.01);
			rtray.P.set(position.x + smalldistancer.x,
					position.y + smalldistancer.y,
					position.z + smalldistancer.z, position.w);

			Colour rtrayCol = raytrace(rtray, level - 1);

			//			rtrayCol.multiply(0.6);
			//			if(rtrayCol.red > 0.26){
			//				rtrayCol.red = 0.0;
			//				rtrayCol.green = 0.0;
			//				rtrayCol.blue = 0.0;
			//			}
			//			if(krr > 99){
			//				col.red += rtrayCol.red * kr.red;
			//				col.green += rtrayCol.green * kr.green;
			//				col.blue += rtrayCol.blue * kr.green;
			//			}else {
			//				if(totalInternalReflection != 1){
			//					col = col.add(rtrayCol.multiply(krr));
			//					if(krr > 0.98 || krr < 0.04){
			//						std::cout << krr<<std::endl;
			//					}
			//				}else{
			//					col.red += rtrayCol.red * kr.red;
			//					col.green += rtrayCol.green * kr.green;
			//					col.blue += rtrayCol.blue * kr.green;
			//				}
			//			}


			col.red += rtrayCol.red * kr.red;
			col.green += rtrayCol.green * kr.green;
			col.blue += rtrayCol.blue * kr.green;

			//				std::cout << krr<<std::endl;
			//			if(rtrayCol.blue > 0.00001 || rtrayCol.blue < -0.00001){
			//				std::cout << rtrayCol.blue<<std::endl;
			//			}
		}

	}

	return col;
}

bool Scene::shadowtrace(Ray &ray, Object *obja, double tlimit) {
	Object *obj;
	Hit hit;

	obj = obj_list;

	while (obj != (Object *) 0) {
		if (obj->intersect(ray, &hit) == true) {
			//				if(obja == obj){
			//					return false;
			//				}
			double shadowraydistance = hit.p.distance(ray.P);
			if (shadowraydistance < tlimit) {
				return true;
			}
		}
		obj = obj->next();
	}

	return false;
}
